package org.pada.domain.event;


import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author pengshen
 * @date 2022.05.21 08:16
 */
@Component
@RequiredArgsConstructor
public class EventHub {

    private static final Map<Class, List<IEventHandler>> EVENT_HANDLER_MAP =
            new ConcurrentHashMap<>();

    private final List<IEventHandler> iEventHandlerList;

    @Component
    private static class DefaultEventHandler implements IEventHandler<AbstractDomainEvent> {

        @Override
        public void doHandle(AbstractDomainEvent event) {

        }
    }

    public static List<IEventHandler> getEventHandler(
            Class<? extends AbstractDomainEvent> clazz) {
        final List<IEventHandler> eventHandlerList = findEventHandler(clazz);
        if (CollectionUtils.isEmpty(eventHandlerList)) {
            //log.warn("no event handler found");
        }
        return eventHandlerList;
    }

    private static List<IEventHandler> findEventHandler(
            Class<? extends AbstractDomainEvent> clazz) {
        if (!EVENT_HANDLER_MAP.containsKey(clazz)) {
            return Collections.emptyList();
        }
        return EVENT_HANDLER_MAP.get(clazz);
    }

    @PostConstruct
    public void register() {
        iEventHandlerList.forEach(x -> {
            final Class eventType = getEventType(x.getClass());
            register(eventType, x);
        });
    }

    private Class getEventType(Class<?> eventHandlerClz) {
        Method[] methods = eventHandlerClz.getDeclaredMethods();
        for (Method method : methods) {
            if (isHandleMethod(method)) {
                return checkAndGetEventParamType(method);
            }
        }
        throw new RuntimeException("Event param in " + eventHandlerClz + " " + "doHandle() is not detected");
    }

    private boolean isHandleMethod(Method method) {
        return "doHandle".equals(method.getName()) && !method.isBridge();
    }

    private Class checkAndGetEventParamType(Method method) {
        Class<?>[] exeParams = method.getParameterTypes();
        if (exeParams.length == 0) {
            throw new RuntimeException("Execute method in " + method.getDeclaringClass()
                    + " should at least have one parameter");
        }
        if (!AbstractDomainEvent.class.isAssignableFrom(exeParams[0])) {
            throw new RuntimeException(
                    "Execute method in " + method.getDeclaringClass() + " should be the subClass of Event");
        }
        return exeParams[0];
    }

    private <T extends AbstractDomainEvent> void register(Class<T> clazz,
                                                          IEventHandler<T> handler) {
        if (EVENT_HANDLER_MAP.containsKey(clazz)) {
            EVENT_HANDLER_MAP.get(clazz).add(handler);
        } else {
            List<IEventHandler> handlerList = new LinkedList<>();
            handlerList.add(handler);
            EVENT_HANDLER_MAP.put(clazz, handlerList);
        }
    }
}
